Skip to content

Feature node libs#140

Merged
kalwalt merged 45 commits into
devfrom
feature-node-libs
May 28, 2026
Merged

Feature node libs#140
kalwalt merged 45 commits into
devfrom
feature-node-libs

Conversation

@kalwalt

@kalwalt kalwalt commented Mar 12, 2022

Copy link
Copy Markdown
Member

jsartoolkitNFT in a Node.js environment

Continues from #128 / #129. Adds an experimental Node.js build of jsartoolkitNFT so NFT marker detection can run server-side (no browser, camera or <canvas>), driven by static image data. Files are loaded through emscripten NODEFS (FORCE_FILESYSTEM + NODEFS/FS), which is the main difference from the browser build.

⚠️ Experimental. The Node API may change without notice and is not yet recommended for production use.

What's in this PR

Node library

  • New TypeScript sources for Node: src/ARControllerNFT_node.ts, src/ARToolkitNFT_node.ts, src/index_node.ts, src/factoryFunctions/initARToolkitNFT_node.ts, src/abstractions/IARToolkitNFT_node.ts (+ generated types/).
  • tools/makem.js: new compile_wasm_node target → build/artoolkitNFT_node_wasm.js (emscripten with ENVIRONMENT=node, FORCE_FILESYSTEM, NODEFS/FS, -lnodefs.js).
  • webpack.config.js: new node target (target: "node") → self-contained dist/ARToolkitNFT_node.js, emitted as commonjs2 (leaner than UMD for a Node-only artifact).

Package consumption

  • Added a conditional exports map: require('@webarkit/jsartoolkit-nft') resolves to the Node build in Node and the UMD build in browsers/bundlers.
  • Named subpaths /simd, /td, /node, plus a ./dist/* wildcard so existing deep-imports keep working. <script> / importScripts / CDN URLs are unaffected by exports.

Examples (examples/node/)

  • example_dist.js — sharp + the Node build (also wired as npm run test-node-example).
  • example_canvas.js — the canvas package, requestAnimationFrame loop.
  • Includes the pinball NFT dataset, camera_para.dat, and pinball-demo.jpg.

Fixes & cleanup

  • Fixed NFT detection in the node examples: process() expects RGBA pixel data (sharp().ensureAlpha().raw()), and must run after the marker loads (tracking needs several iterations to lock on).
  • Fixed getNFTMarker: dispatchEvent() now also notifies EventEmitter listeners registered via on() (previously on() and dispatchEvent() used two disconnected registries, so the event never fired).
  • Removed per-frame debug logging (multi-MB typed-array dumps) from the node process() loop; examples now log the concise marker data.
  • Removed obsolete node-src/ (an earlier no-webpack attempt) in favor of the canonical TS → dist path.

Maintenance

  • Merged latest master into the branch (it was well behind) and rebuilt the libs; synced .gitignore; removed unused pinball-test* images.
  • README: new "Node.js (experimental)" section mirroring the Python bindings one, plus docs for the exports map / subpaths.

Usage

const { ARControllerNFT } = require('@webarkit/jsartoolkit-nft'); // -> node build in Node
const sharp = require('sharp');

const arControllerNFT = await new ARControllerNFT(2000, 1500, '/camera_para.dat');
const ar = await arControllerNFT._initialize();

const data = await sharp('pinball-demo.jpg').ensureAlpha().raw().toBuffer(); // RGBA
const imageData = new Uint8Array(data.buffer);

ar.on('getNFTMarker', (e) => console.log('NFT marker detected:', e.data.marker));
ar.loadNFTMarker('DataNFT/pinball', (id) => {
  ar.trackNFTMarkerId(id);
  for (let i = 0; i < 10; i++) ar.process(imageData);
});

Verification

  • npm run build-docker (emscripten) rebuilds all wasm targets incl. artoolkitNFT_node_wasm.js.
  • npm run build-ts (webpack) rebuilds dist for all 4 targets (default / simd / td / node).
  • examples/node/example_dist.js and example_canvas.js detect the pinball marker.

Tasks

  • develop a unique lib for node
  • remove unnecessary code for node
  • implement the basic methods to run a simple app

Follow-ups (separate PRs)

  • ESM Node build (output.library.type: "module") so named import { ... } works — currently CommonJS, so use require() / default import.
  • Trim the npm package (e.g. exclude the 3.4 MB debug build and the orphaned standalone node wasm).
  • Live camera capture example (current examples process a single static image).

@kalwalt

kalwalt commented Apr 1, 2022

Copy link
Copy Markdown
Member Author

The new loadCamera function now can run without errors but i don't receive a message like:
artoolkitNFT_ES6_wasm.js:9 [warning] *** Camera Parameter resized from 640, 480. ***
This should come from setCamera() inside setup():

int setup(int width, int height, int cameraID) {
int id = gARControllerID++;
arController *arc = &(arControllers[id]);
arc->id = id;
arc->width = width;
arc->height = height;
arc->videoFrameSize = width * height * 4 * sizeof(ARUint8);
arc->videoFrame = (ARUint8*) malloc(arc->videoFrameSize);
arc->videoLuma = (ARUint8*) malloc(arc->videoFrameSize / 4);
setCamera(id, cameraID);
webarkitLOGi("Allocated videoFrameSize %d", arc->videoFrameSize);
EM_ASM_({
if (!artoolkitNFT["frameMalloc"]) {
artoolkitNFT["frameMalloc"] = ({});
}
var frameMalloc = artoolkitNFT["frameMalloc"];
frameMalloc["framepointer"] = $1;
frameMalloc["framesize"] = $2;
frameMalloc["camera"] = $3;
frameMalloc["transform"] = $4;
frameMalloc["videoLumaPointer"] = $5;
},
arc->id,
arc->videoFrame,
arc->videoFrameSize,
arc->cameraLens,
gTransform,
arc->videoLuma //$5
);
return arc->id;
}

setCamera:
int setCamera(int id, int cameraID) {
if (arControllers.find(id) == arControllers.end()) { return -1; }
arController *arc = &(arControllers[id]);
if (cameraParams.find(cameraID) == cameraParams.end()) { return -1; }
arc->param = cameraParams[cameraID];
if (arc->param.xsize != arc->width || arc->param.ysize != arc->height) {
ARLOGw("*** Camera Parameter resized from %d, %d. ***\n", arc->param.xsize, arc->param.ysize);
arParamChangeSize(&(arc->param), arc->width, arc->height, &(arc->param));
}
// ARLOGi("*** Camera Parameter ***\n");
// arParamDisp(&(arc->param));
deleteHandle(arc);
if ((arc->paramLT = arParamLTCreate(&(arc->param), AR_PARAM_LT_DEFAULT_OFFSET)) == NULL) {
webarkitLOGe("setCamera(): Error: arParamLTCreate.");
return -1;
}
// ARLOGi("setCamera(): arParamLTCreated\n..%d, %d\n", (arc->paramLT->param).xsize, (arc->paramLT->param).ysize);
// setup camera
if ((arc->arhandle = arCreateHandle(arc->paramLT)) == NULL) {
webarkitLOGe("setCamera(): Error: arCreateHandle.");
return -1;
}
// AR_DEFAULT_PIXEL_FORMAT
int set = arSetPixelFormat(arc->arhandle, arc->pixFormat);
arc->ar3DHandle = ar3DCreateHandle(&(arc->param));
if (arc->ar3DHandle == NULL) {
webarkitLOGe("setCamera(): Error creating 3D handle");
return -1;
}
arglCameraFrustumRH(&((arc->paramLT)->param), arc->nearPlane, arc->farPlane, arc->cameraLens);
arc->kpmHandle = createKpmHandle(arc->paramLT);
return 0;

It could depends by the type of printings, maybe stream by ARLOGw is not outputted in node?

@kalwalt

kalwalt commented Apr 1, 2022

Copy link
Copy Markdown
Member Author

i make a simple test, i added a simple std::cout message before:

if (arc->param.xsize != arc->width || arc->param.ysize != arc->height) { 
 		ARLOGw("*** Camera Parameter resized from %d, %d. ***\n", arc->param.xsize, arc->param.ysize); 
 		arParamChangeSize(&(arc->param), arc->width, arc->height, &(arc->param)); 
 	}

and i get the message in the console, so i assume that for some reason simply the camera is not resized.

@kalwalt kalwalt self-assigned this Apr 1, 2022
@kalwalt

kalwalt commented Jun 29, 2022

Copy link
Copy Markdown
Member Author

Testing new node listeners, but actually the code (see node/example.js) can't detect and track the pinball image. If the data passed to the process function it should print that the nft marker is tracked, Instead i got a NftMarkerInfo struct with a not found marker. When i have a bit of time i will try to debug why is not detecting.

@kalwalt

kalwalt commented Sep 30, 2022

Copy link
Copy Markdown
Member Author

In regards of the previous comment: this could be caused because artoolkitNFT.NFTMarkerInfo Is not attached to the global scope when It Is passed through the EM_ASM_ inside the getNFTMarkerInfo function (ARToolKitJS.cpp). This Is a reminder when i come back from holiday.

@kalwalt

kalwalt commented Feb 19, 2025

Copy link
Copy Markdown
Member Author

I improved the code with a new dist dile specyfic for node, i added two new wxamples but still they can't detect the marker. Initially i thought that i was supplying the imnage data in a wrong way, but i'm not sure of this anymore. There is some bug somewhere in the code, maybe i can add some more debug printings in the C++ code?

Comment thread src/ARToolkitNFT_node.ts
Comment thread src/ARToolkitNFT_node.ts Outdated
kalwalt and others added 9 commits February 19, 2025 13:50
process() expects RGBA pixel data, so decode with sharp's ensureAlpha()
instead of RGB/JPEG bytes, and run process() after the marker loads in a
short loop so NFT tracking can lock on. Clean up the examples and add the
pinball-demo.jpg test image they depend on.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restore the IDE config, build artifact, and python-bindings ignore rules
that are present on master but missing from this branch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Align example_canvas.js with the other node examples: same test image
and controller dimensions.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
# Conflicts:
#	build/artoolkitNFT.debug.js
#	build/artoolkitNFT.min.js
#	build/artoolkitNFT_embed_ES6_wasm.js
#	build/artoolkitNFT_wasm.js
#	build/artoolkitNFT_wasm.simd.js
#	dist/ARToolkitNFT.js
#	dist/ARToolkitNFT_simd.js
#	dist/ARToolkitNFT_td.js
#	emscripten/ARToolKitJS.cpp
#	package-lock.json
#	package.json
#	src/Utils.ts
#	tools/makem.js
#	types/src/Utils.d.ts
#	webpack.config.js
Add "types": ["node"] so the node sources (which use require) type-check;
without it ts-loader fails with TS2591. Rebuild the node wasm and dist
bundle from the merged source via docker + webpack, and regenerate the
lockfile to include canvas/sharp.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@kalwalt

kalwalt commented May 28, 2026

Copy link
Copy Markdown
Member Author

Finally with commit 409b7da the examples detect the nft pinball marker, it's a great result!

kalwalt and others added 7 commits May 28, 2026 16:26
Delete the obsolete pinball-test images (no longer referenced by any
example) and ignore emscripten/zlib/build.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The node bundle is Node-only, so commonjs2 is leaner and more idiomatic
than umd (drops the dead AMD/global-object branches). Consumption is
unchanged: require() and default import still work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Node now resolves require('@webarkit/jsartoolkit-nft') to the node build
while browsers/bundlers get the UMD build. Adds named subpaths /simd, /td,
/node, keeps the ./dist/* wildcard so existing deep-imports (and script
tags / importScripts, which bypass exports) keep working.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Explain per-environment resolution (browser UMD vs Node build), the
/simd, /td, /node subpaths, and that script/importScripts URLs are
unaffected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
node-src was an earlier no-webpack node implementation that directly
required build/artoolkitNFT_node_wasm.js. The TypeScript sources
(src/*_node.ts -> dist/ARToolkitNFT_node.js) are the canonical node path
wired into the package exports, so drop the duplicate node-src and the
example.js that used it (example_dist.js covers the same flow via dist).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a Node.js (experimental) section mirroring the Python bindings one:
marks it experimental, shows require('@webarkit/jsartoolkit-nft') usage
with a small sharp-based example, lists what works / is not yet done, and
points to examples/node (example_dist.js, example_canvas.js).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove the per-frame debug dumps (sourceImage, videoSize, videoluma,
nftMarkerInfo) from the node controller's process loop, which flooded the
console with multi-million-element typed arrays. Also fix dispatchEvent to
notify EventEmitter listeners registered via on() (it only called the
separate addEventListener registry before), so the examples' getNFTMarker
handler actually fires. Examples now log the concise marker data instead
of the whole event (which carried the controller + its buffers).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant